var graph;
// 工作流元素类型
var cellType = {
    start: "0",//开始节点
    end: "Y",//结束节点
    edge: "edge",//连接线
    audit: "1"//审核节点
}

$(function () {
    //初始化mxgraph控件
    var container = document.getElementById('graphContainer');
    initMxGraph(container);

    //api插入节点
    //insertVertex();

    //json转xml加载
    var xml = jsonToXml([
        { id: '001', type: cellType.start, label: '开始', pointX: '0', pointY: '0' },
        { id: '002', type: cellType.edge, label: '连接线', pointX: '', pointY: '', sourceId: '001', targetId: '003' },
        { id: '003', type: cellType.audit, label: '审核', pointX: '200', pointY: '0', image: 'audit-node.png', status: '1' },
        { id: '004', type: cellType.edge, label: '连接线', pointX: '250,300,350', pointY: '50,100,50', sourceId: '003', targetId: '005' },
        { id: '005', type: cellType.audit, label: '审核', pointX: '400', pointY: '0', image: 'audit-node.png', status: '0' },
        { id: '006', type: cellType.edge, label: '连接线', pointX: '', pointY: '', sourceId: '005', targetId: '007' },
        { id: '007', type: cellType.end, label: '开始', pointX: '600', pointY: '0' },
    ]);
    importModelXML(xml);

    //设置初始滚动条位置
    initScroll();
});

//api插入节点
function insertVertex() {
    var parent = graph.getDefaultParent();
    var w = 50;
    var h = 50;
    graph.getModel().beginUpdate();
    try {
        //插入一个节点
        var v1 = graph.insertVertex(parent, null, '开始', 0, 0, w, h);
        //设置节点自定义值
        v1.data = {
            type: cellType.start,
            label: "开始"
        };

        var v2 = graph.insertVertex(parent, null, '审核节点', 0, 0, w, h);
        v2.data = {
            type: cellType.audit,
            label: "审核节点"
        };

        var v3 = graph.insertVertex(parent, null, '结束', 0, 0, w, h);
        v3.data = {
            type: cellType.end,
            label: "结束"
        };
        //插入节点连接线
        var e1 = graph.insertEdge(parent, null, '连接线', v1, v2);
        e1.data = {
            label: "连接线",
            type: cellType.edge
        };
        var e2 = graph.insertEdge(parent, null, '连接线', v2, v3);
        e2.data = {
            label: "连接线",
            type: cellType.edge
        };
    } finally {
        //添加动画
        var morph = new mxMorphing(graph);
        morph.addListener(mxEvent.DONE, function () {
            graph.getModel().endUpdate();
            //随机布局
            autoLayout('mxFastOrganicLayout');
        });
        morph.startAnimation();
    }
}

//保存
function save() {
    alert('控制台查看');
    console.table(xmlToJson());
}
/****************************初始化MxGraph*******************************/
//初始化MxGraph
function initMxGraph(container) {
    // 禁用鼠标右键
    mxEvent.disableContextMenu(container);

    //判断浏览器是否支持
    if (!mxClient.isBrowserSupported()) {
        mxUtils.error('浏览器不支持mxGraph插件!', 200, false);
        return;
    }

    // 定义一个连接图标，这将自动禁用源的突出瞄点
    mxConnectionHandler.prototype.connectImage = new mxImage(baseImgSrc + 'connector.png', 26, 26);

    var outline = document.getElementById('outlineContainer');

    // 解决方案为Internet Explorer忽略某些风格
    if (mxClient.IS_QUIRKS) {
        document.body.style.overflow = 'hidden';
        new mxDivResizer(tbContainer);
        new mxDivResizer(container);
        new mxDivResizer(outline);
    }

    //创建容器内的模型和图表
    // var model = new mxGraphModel();
    // var graph = new mxGraph(container, model);
    graph = new mxGraph(container);

    //设置导航
    new mxOutline(graph, outline);

    //初始化设置
    setDefaultConfig();

    //设置默认样式
    setDefautStyle();

    //设置样式名称
    configureStylesheet();

    //设置节点内容展示
    setShowLable();

    //设置节点提示
    setTip();

    //扩展画布
    extendCanvas();

    // 工具栏事件绑定
    bindToolbarEven();

    //设置鼠标停留图标
    setHoverIcons();

    //右键菜单
    rightClickMenu();

    // 监听事件
    listenEvent();

    //连接边校验
    setConnectValidation();
}

//初始化设置
function setDefaultConfig() {
    // 连接
    graph.setConnectable(true);
    // 提示信息
    graph.setTooltips(true);
    // 父节点可折叠
    graph.foldingEnabled = false;

    // 设置false为只读
    graph.setEnabled(true);

    // 节点可编辑
    graph.setCellsEditable(false);

    // 右键移动容器坐标轴
    graph.setPanning(true);

    // 容器大小自适应
    graph.setResizeContainer(false);

    // 网格尺寸越大，布局结果越清晰
    graph.gridSize = 40;

    // 动态改变样式
    graph.getView().updateStyle = true;

    // 重复连接
    graph.setMultigraph(false);

    // Label 将显示 Html 格式的 Value
    graph.setHtmlLabels(true);

    // 移动 Vertex 的 Label
    // graph.setVertexLabelsMovable(true);

    // 禁止改变元素大小
    graph.setCellsResizable(false);
    // 连线的目标和源是同一元素
    graph.setAllowLoops(false);

    // 鼠标框选
    new mxRubberband(graph);

    // 拖拽过程对齐线
    mxGraphHandler.prototype.guidesEnabled = true;

    // 禁止游离线条
    graph.setDisconnectOnMove(false);
    graph.setAllowDanglingEdges(false);
    mxGraph.prototype.isCellMovable = function (cell) {
        return !cell.edge
    };

    // 使用调整线条弯曲度
    graph.setCellsBendable(true);

    // 禁止从将label从线条上拖离
    mxGraph.prototype.edgeLabelsMovable = false;
}

//设置默认样式
function setDefautStyle() {
    //默认节点样式
    var vStyle = [];
    // 定义形状
    vStyle[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_LABEL;// 不设置这个属性 背景图片不出来
    //是否圆角
    vStyle[mxConstants.STYLE_ROUNDED] = true;
    //边框颜色
    vStyle[mxConstants.STYLE_STROKECOLOR] = 'gray';
    //填充颜色
    vStyle[mxConstants.STYLE_FILLCOLOR] = '#EEEEEE';
    //渐变色
    vStyle[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
    //阴影
    vStyle[mxConstants.STYLE_SHADOW] = true;
    //文字颜色
    vStyle[mxConstants.STYLE_FONTCOLOR] = '#000000';
    //文字大小
    vStyle[mxConstants.STYLE_FONTSIZE] = '12';
    // 文字样式：粗体（1），斜体（2）和下划线（4）的ORed位模式，即粗体下划线是“5”。
    vStyle[mxConstants.STYLE_FONTSTYLE] = 1;
    //文字水平对齐
    vStyle[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
    //字体垂直对齐
    vStyle[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_TOP;
    //设置文本相对节点显示位置
    vStyle[mxConstants.STYLE_VERTICAL_LABEL_POSITION] = mxConstants.ALIGN_BOTTOM;
    // 底图水平对齐
    vStyle[mxConstants.STYLE_IMAGE_ALIGN] = mxConstants.ALIGN_CENTER;
    // 底图垂直对齐
    vStyle[mxConstants.STYLE_IMAGE_VERTICAL_ALIGN] = mxConstants.ALIGN_CENTER;
    // 图片路径
    vStyle[mxConstants.STYLE_IMAGE] = baseImgSrc + "audit-node.png";
    // 背景图片宽
    vStyle[mxConstants.STYLE_IMAGE_WIDTH] = 50;
    // 背景图片高
    vStyle[mxConstants.STYLE_IMAGE_HEIGHT] = 50;
    // 四边间距设置
    vStyle[mxConstants.STYLE_SPACING] = 10;

    vStyle[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
    graph.getStylesheet().putDefaultVertexStyle(vStyle);

    // 默认线条样式
    var eStyle = [];
    eStyle[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;//形状 连接线
    eStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;//线条样式
    eStyle[mxConstants.STYLE_STROKECOLOR] = '#18a1e2';//连线颜色
    eStyle[mxConstants.STYLE_STROKEWIDTH] = 2;//线宽度
    eStyle[mxConstants.STYLE_FONTSTYLE] = 1;//加粗
    eStyle[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;//字体水平对齐
    eStyle[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;//字体垂直对齐
    eStyle[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;//结尾，经典箭头标记
    eStyle[mxConstants.STYLE_FONTSIZE] = '10';//字体颜色
    eStyle[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = '#ffffff';//字体背景色
    eStyle[mxConstants.STYLE_ROUNDED] = true;// 设置线条拐弯处为圆角

    graph.getStylesheet().putDefaultEdgeStyle(eStyle);

    // 设置拖拽线的过程出现折线，默认为直线
    graph.connectionHandler.createEdgeState = function () {
        var edge = graph.createEdge();
        return new mxCellState(graph.view, edge, graph.getCellStyle(edge));
    };
}

//设置样式名称
function configureStylesheet() {
    graph.getStylesheet().putCellStyle('rounded', {
        shape: "rounded",
        rounded: 1,
        whiteSpace: "wrap"
    });
    graph.getStylesheet().putCellStyle('ellipse', {
        shape: "ellipse",
        rounded: 1,
        whiteSpace: "wrap"
    });
    graph.getStylesheet().putCellStyle('rhombus', {
        shape: "rhombus",
        rounded: 1,
        whiteSpace: "wrap"
    });
    graph.getStylesheet().putCellStyle('triangle', {
        shape: "triangle",
        rounded: 1,
        whiteSpace: "wrap"
    });
    graph.getStylesheet().putCellStyle('cylinder', {
        shape: "cylinder",
        rounded: 1,
        whiteSpace: "wrap"
    });
    graph.getStylesheet().putCellStyle('actor', {
        shape: "actor",
        rounded: 1,
        whiteSpace: "wrap"
    });
};

//设置节点内容展示
function setShowLable() {
    graph.htmlLabels = true;
    graph.getLabel = function (cell) {
        return cell.getValue();
    };
}

//设置节点提示
function setTip() {
    graph.getTooltipForCell = function (cell) {
        return cell.data.label;
    }
}

//扩展画布
function extendCanvas() {
    /**
     * 设置缓存区
    */
    graph.scrollTileSize = new mxRectangle(0, 0, 400, 400);

    /**
     * 返回滚动条内的填充页大小
     */
    graph.getPagePadding = function () {
        return new mxPoint(Math.max(0, Math.round(graph.container.offsetWidth - 34)),
            Math.max(0, Math.round(graph.container.offsetHeight - 34)));
    };

    /**
     * 返回缩放页面大小
     */
    graph.getPageSize = function () {
        return (this.pageVisible) ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale,
            this.pageFormat.height * this.pageScale) : this.scrollTileSize;
    };

    /**
     * 返回一个描述背景页位置和缓存区矩形的计数，
     * 其中x和y是上、左页的位置，
     * width和height是垂直和水平页缓存区矩形计数。
     */
    graph.getPageLayout = function () {
        var size = (this.pageVisible) ? this.getPageSize() : this.scrollTileSize;
        var bounds = this.getGraphBounds();
        if (bounds.width == 0 || bounds.height == 0) {
            return new mxRectangle(0, 0, 1, 1);
        }
        else {
            var x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
            var y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
            var w = Math.floor(bounds.width / this.view.scale);
            var h = Math.floor(bounds.height / this.view.scale);

            var x0 = Math.floor(x / size.width);
            var y0 = Math.floor(y / size.height);
            var w0 = Math.ceil((x + w) / size.width) - x0;
            var h0 = Math.ceil((y + h) / size.height) - y0;

            return new mxRectangle(x0, y0, w0, h0);
        }
    };
    // Fits the number of background pages to the graph
    graph.view.getBackgroundPageBounds = function () {
        var layout = this.graph.getPageLayout();
        var page = this.graph.getPageSize();

        return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
            this.scale * (this.translate.y + layout.y * page.height),
            this.scale * layout.width * page.width,
            this.scale * layout.height * page.height);
    };

    graph.getPreferredPageSize = function (bounds, width, height) {
        var pages = this.getPageLayout();
        var size = this.getPageSize();

        return new mxRectangle(0, 0, pages.width * size.width, pages.height * size.height);
    };

    /**
     * Guesses autoTranslate to avoid another repaint (see below).
     * Works if only the scale of the graph changes or if pages
     * are visible and the visible pages do not change.
     */
    var graphViewValidate = graph.view.validate;
    graph.view.validate = function () {
        if (this.graph.container != null && mxUtils.hasScrollbars(this.graph.container)) {
            var pad = this.graph.getPagePadding();
            var size = this.graph.getPageSize();

            // Updating scrollbars here causes flickering in quirks and is not needed
            // if zoom method is always used to set the current scale on the graph.
            var tx = this.translate.x;
            var ty = this.translate.y;
            this.translate.x = pad.x / this.scale - (this.x0 || 0) * size.width;
            this.translate.y = pad.y / this.scale - (this.y0 || 0) * size.height;
        }

        graphViewValidate.apply(this, arguments);
    };

    var graphSizeDidChange = graph.sizeDidChange;
    graph.sizeDidChange = function () {
        if (this.container != null && mxUtils.hasScrollbars(this.container)) {
            var pages = this.getPageLayout();
            var pad = this.getPagePadding();
            var size = this.getPageSize();

            // Updates the minimum graph size
            var minw = Math.ceil(2 * pad.x / this.view.scale + pages.width * size.width);
            var minh = Math.ceil(2 * pad.y / this.view.scale + pages.height * size.height);

            var min = graph.minimumGraphSize;

            // LATER: Fix flicker of scrollbar size in IE quirks mode
            // after delayed call in window.resize event handler
            if (min == null || min.width != minw || min.height != minh) {
                graph.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
            }

            // Updates auto-translate to include padding and graph size
            var dx = pad.x / this.view.scale - pages.x * size.width;
            var dy = pad.y / this.view.scale - pages.y * size.height;

            if (!this.autoTranslate && (this.view.translate.x != dx || this.view.translate.y != dy)) {
                this.autoTranslate = true;
                this.view.x0 = pages.x;
                this.view.y0 = pages.y;
                // NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
                // BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
                // SHOULD MOVE TRANSLATE/SCALE TO VIEW.
                var tx = graph.view.translate.x;
                var ty = graph.view.translate.y;
                graph.view.setTranslate(dx, dy);
                graph.container.scrollLeft += (dx - tx) * graph.view.scale;
                graph.container.scrollTop += (dy - ty) * graph.view.scale;
                this.autoTranslate = false;
                return;
            }
            graphSizeDidChange.apply(this, arguments);
        }
    };
}

/****************************工具栏*******************************/
// 工具栏实体
function getMenuItems() {
    return [
        {
            id: "startNode",//界面绑定节点的id
            imgSrc: baseImgSrc + "start-node.png",//节点拖拽图片
            width: 50,
            height: 50,
            style: "image=" + baseImgSrc + "start-node.png;",//节点样式
            //自定义数据
            data: {
                type: cellType.start,
                label: "开始"
            }
        },
        {
            id: "endNode",
            imgSrc: baseImgSrc + "end-node.png",
            width: 50,
            height: 50,
            style: "image=" + baseImgSrc + "end-node.png;",
            data: {
                type: cellType.end,
                label: "结束"
            }
        },
        {
            id: "auditNode",
            imgSrc: baseImgSrc + "audit-node.png",
            width: 50,
            height: 50,
            style: "image=" + baseImgSrc + "audit-node.png;",
            data: {
                type: cellType.audit,
                label: "审核节点"
            }
        }
    ];
}

//工具栏事件绑定
function bindToolbarEven() {
    //获取菜单
    var menuItems = getMenuItems();

    // 判断drop是否有效
    var dropValidate = function (evt) {
        var x = mxEvent.getClientX(evt);
        var y = mxEvent.getClientY(evt);
        // 获取 x,y 所在的元素
        var elt = document.elementFromPoint(x, y);
        // 如果鼠标落在graph容器
        if (mxUtils.isAncestorNode(graph.container, elt)) {
            return graph;
        }
        // 鼠标落在其他地方
        return null;
    };

    // drop成功后新建一个节点
    var dropSuccessCb = function (graph, evt, target, x, y) {
        var id = this.element.getAttribute('id');
        var item;
        for (var index = 0; index < menuItems.length; index++) {
            var obj = menuItems[index];
            if (obj.id == id) {
                item = obj;
                break;
            }
        }
        var cell = new mxCell(item.data.label, new mxGeometry(0, 0, item.width, item.height), item.style);
        cell.vertex = true;
        // 自定义的业务数据放在 data 属性
        cell.data = item.data;
        //插入数据
        var cells = graph.importCells([cell], x, y, target);
        if (cells != null && cells.length > 0) {
            //这种节点为选中
            graph.setSelectionCells(cells);
        }
    };

    for (var index = 0; index < menuItems.length; index++) {
        var item = menuItems[index];
        var img = document.getElementById(item.id);
        //创建拖动预览
        var dragElt = document.createElement('img');
        dragElt.setAttribute('src', item.imgSrc);
        dragElt.setAttribute('style', 'width:' + item.width + 'px;height:' + item.height + 'px;');
        //添加拖拽事件
        mxUtils.makeDraggable(img, dropValidate, dropSuccessCb, dragElt,
            null, null, graph.autoscroll, true);
    }
}

/****************************右键菜单和停留图标*******************************/
//设置鼠标停留图标
function setHoverIcons() {
    var iconTolerance = 20;

    var mxIconSet = function (state) {
        this.images = [];
        var graph = state.view.graph;

        // 复制图标
        var img = mxUtils.createImage(baseImgSrc + 'copy.png');
        img.setAttribute('title', '复制');
        img.style.position = 'absolute';
        img.style.cursor = 'pointer';
        img.style.width = '16px';
        img.style.height = '16px';
        img.style.left = (state.x + state.width) + 'px';
        img.style.top = (state.y - 16) + 'px';

        mxEvent.addGestureListeners(img,
            mxUtils.bind(this, function (evt) {
                copyNode(state.cell);
                mxEvent.consume(evt);
                this.destroy();
            })
        );

        state.view.graph.container.appendChild(img);
        this.images.push(img);

        // 删除图标
        var img = mxUtils.createImage(baseImgSrc + 'delete.png');
        img.setAttribute('title', '删除');
        img.style.position = 'absolute';
        img.style.cursor = 'pointer';
        img.style.width = '16px';
        img.style.height = '16px';
        img.style.left = (state.x + state.width + 16) + 'px';
        img.style.top = (state.y - 16) + 'px';

        mxEvent.addGestureListeners(img,
            mxUtils.bind(this, function (evt) {
                mxEvent.consume(evt);
            })
        );

        mxEvent.addListener(img, 'click',
            mxUtils.bind(this, function (evt) {
                deleteNode(state.cell);
                mxEvent.consume(evt);
                this.destroy();
            })
        );

        state.view.graph.container.appendChild(img);
        this.images.push(img);
    };
    mxIconSet.prototype.destroy = function () {
        if (this.images != null) {
            for (var i = 0; i < this.images.length; i++) {
                var img = this.images[i];
                img.parentNode.removeChild(img);
            }
        }

        this.images = null;
    };
    //监听鼠标，显示销毁图标
    graph.addMouseListener(
        {
            currentState: null,
            currentIconSet: null,
            mouseDown: function (sender, me) {
                //
                if (this.currentState != null) {
                    this.dragLeave(me.getEvent(), this.currentState);
                    this.currentState = null;
                }
            },
            mouseMove: function (sender, me) {
                if (this.currentState != null && (me.getState() == this.currentState ||
                    me.getState() == null)) {
                    var tol = iconTolerance;
                    var tmp = new mxRectangle(me.getGraphX() - tol,
                        me.getGraphY() - tol, 2 * tol, 2 * tol);
                    if (mxUtils.intersects(tmp, this.currentState)) {
                        return;
                    }
                }

                var tmp = graph.view.getState(me.getCell());

                // Ignores everything but vertices
                if (graph.isMouseDown || (tmp != null && !graph.getModel().isVertex(tmp.cell))) {
                    tmp = null;
                }
                if (tmp != this.currentState) {
                    if (this.currentState != null) {
                        this.dragLeave(me.getEvent(), this.currentState);
                    }

                    this.currentState = tmp;

                    if (this.currentState != null) {
                        this.dragEnter(me.getEvent(), this.currentState);
                    }
                }
            },
            mouseUp: function (sender, me) { },
            dragEnter: function (evt, state) {
                if (this.currentIconSet == null) {
                    this.currentIconSet = new mxIconSet(state);
                }
            },
            dragLeave: function (evt, state) {
                if (this.currentIconSet != null) {
                    this.currentIconSet.destroy();
                    this.currentIconSet = null;
                }
            }
        });
}

//右键菜单
function rightClickMenu() {
    //自动展开
    graph.popupMenuHandler.autoExpand = true;

    graph.popupMenuHandler.factoryMethod = function (menu, cell, evt) {
        var model = graph.getModel();

        if (cell != null) {
            //是否为节点
            if (model.isVertex(cell)) {
                menu.addItem('复制', baseImgSrc + 'copy.png', function () {
                    copyNode(cell);
                });
                menu.addItem('删除', baseImgSrc + 'delete.png', function () {
                    deleteNode(cell);
                });
                //添加分隔栏
                menu.addSeparator();
            }
        }
        menu.addItem('打印预览', baseImgSrc + 'printer.png', function () {
            printPreview();
        });
        menu.addItem('显示打印页框', baseImgSrc + 'fit_to_size.png', function () {
            showPage();
        });
        //添加分隔栏
        menu.addSeparator();

        menu.addItem('放大', baseImgSrc + 'zoom_in.png', function () {
            setViewScale(1);
        });
        menu.addItem('缩小', baseImgSrc + 'zoom_out.png', function () {
            setViewScale(-1);
        });
        menu.addItem('还原', baseImgSrc + 'view_1_1.png', function () {
            setViewScale(0);
        });
        //添加分隔栏
        menu.addSeparator();

        var automenu = menu.addItem('自动布局', baseImgSrc + 'layout.png', null);
        menu.addItem('圆形布局', null, function () {
            autoLayout('mxCircleLayout');
        }, automenu);
        menu.addItem('紧凑树形', null, function () {
            autoLayout('mxCompactTreeLayout');
        }, automenu);
        menu.addItem('紧凑树形（垂直）', null, function () {
            autoLayout('mxCompactTreeLayout2');
        }, automenu);
        menu.addItem('随机', null, function () {
            autoLayout('mxFastOrganicLayout');
        }, automenu);
        menu.addItem('分区', null, function () {
            autoLayout('mxPartitionLayout');
        }, automenu);
        menu.addItem('径向树', null, function () {
            autoLayout('mxRadialTreeLayout');
        }, automenu);
        menu.addItem('堆栈', null, function () {
            autoLayout('mxStackLayout');
        }, automenu);
        menu.addItem('组合', null, function () {
            autoLayout('mxCompositeLayout');
        }, automenu);
    };
};

//复制节点
function copyNode(cell) {
    var s = graph.gridSize;
    graph.setSelectionCells(graph.moveCells([cell], s, s, true));
}

//删除节点
function deleteNode(cell, includeChild) {
    var cells = [];
    var tip;
    //删除是否包含子节点
    if (includeChild) {
        //连同子节点一起删除
        graph.traverse(state.cell, true, function (vertex) {
            cells.push(vertex);
            return true;
        });
        tip = "是否删除当前节点（含子节点）？";
    } else {
        cells.push(cell);
        tip = "是否删除当前节点？";
    }
    if (confirm(tip)) {
        graph.removeCells(cells);
    }
}

//打印预览
function printPreview() {
    // 获取缩放比例
    var scale = graph.view.getScale();
    // 页头，页尾
    var headerSize = 50;
    var footerSize = 50;

    // Removes header and footer from page height
    graph.pageFormat.height -= headerSize + footerSize;

    // Takes zoom into account for moving cells
    graph.graphHandler.scaleGrid = true;

    // Applies scale to page
    var pf = mxRectangle.fromRectangle(graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT);
    pf.width = Math.round(pf.width * scale * graph.pageScale);
    pf.height = Math.round(pf.height * scale * graph.pageScale);

    // Finds top left corner of top left page
    var bounds = mxRectangle.fromRectangle(graph.getGraphBounds());
    bounds.x -= graph.view.translate.x * graph.view.scale;
    bounds.y -= graph.view.translate.y * graph.view.scale;

    var x0 = Math.floor(bounds.x / pf.width) * pf.width;
    var y0 = Math.floor(bounds.y / pf.height) * pf.height;

    var preview = new mxPrintPreview(graph, scale, pf, 0, -x0, -y0);
    preview.marginTop = headerSize * scale * graph.pageScale;
    preview.marginBottom = footerSize * scale * graph.pageScale;
    preview.autoOrigin = false;

    var oldRenderPage = preview.renderPage;
    preview.renderPage = function (w, h, x, y, content, pageNumber) {
        var div = oldRenderPage.apply(this, arguments);

        var header = document.createElement('div');
        header.style.position = 'absolute';
        header.style.boxSizing = 'border-box';
        header.style.fontFamily = 'Arial,Helvetica';
        header.style.height = (this.marginTop - 10) + 'px';
        header.style.textAlign = 'center';
        header.style.verticalAlign = 'middle';
        header.style.marginTop = 'auto';
        header.style.fontSize = '12px';
        header.style.width = '100%';

        // Vertical centering for text in header/footer
        header.style.lineHeight = (this.marginTop - 10) + 'px';

        var footer = header.cloneNode(true);

        mxUtils.write(header, '第 ' + pageNumber + ' 页');
        header.style.borderBottom = '1px solid gray';
        header.style.top = '0px';

        mxUtils.write(footer, '第 ' + pageNumber + ' 页');
        footer.style.borderTop = '1px solid gray';
        footer.style.bottom = '0px';

        div.firstChild.appendChild(footer);
        div.firstChild.appendChild(header);

        return div;
    };

    preview.open();
}

//显示打印页框
function showPage() {
    graph.pageBreaksVisible = !graph.pageBreaksVisible;
    graph.sizeDidChange();
}

//设置显示比例 1 放大，-1 缩小 0 还原
function setViewScale(type) {
    if (type == 1) {
        graph.zoomIn();
    } else if (type == -1) {
        graph.zoomOut();
    } else {
        graph.view.setScale(1);
    }
}

//自动布局
function autoLayout(type) {
    var parent = graph.getDefaultParent();
    graph.getModel().beginUpdate();
    try {
        var layout = new mxCompactTreeLayout(graph);
        switch (type) {
            case 'mxCircleLayout'://圆形布局
                layout = new mxCircleLayout(graph);
                break;
            case 'mxCompactTreeLayout'://紧凑树形
                layout = new mxCompactTreeLayout(graph);
                layout.horizontal = true;
                layout.levelDistance = 20;
                layout.nodeDistance = 40;
                break;
            case 'mxCompactTreeLayout2'://紧凑树形（垂直）
                layout = new mxCompactTreeLayout(graph);
                layout.horizontal = false;
                layout.levelDistance = 10;
                // layout.nodeDistance=80;
                break;
            case 'mxFastOrganicLayout'://随机
                layout = new mxFastOrganicLayout(graph);
                layout.forceConstant = 100;
                break;
            case 'mxPartitionLayout'://分区
                layout = new mxPartitionLayout(graph, true, 10, 20);
                break;
            case 'mxRadialTreeLayout'://径向树
                layout = new mxRadialTreeLayout(graph);
                layout.invert = true//反方向
                break;
            case 'mxStackLayout'://堆栈
                layout = new mxStackLayout(graph, true);
                break;
            case 'mxCompositeLayout'://组合
                var first = new mxFastOrganicLayout(graph);
                var second = new mxParallelEdgeLayout(graph);
                var layout = new mxCompositeLayout(graph, [first, second], first);
                layout.execute(graph.getDefaultParent());
                break;
            default:
                layout = new mxGraphLayout(graph);
                break;
        }
        layout.execute(graph.getDefaultParent());
    }
    catch (e) {
        throw e;
    }
    finally {
        //添加动画
        var morph = new mxMorphing(graph);
        morph.addListener(mxEvent.DONE, function () {
            graph.getModel().endUpdate();
            //设置初始滚动条位置
            initScroll();
        });
        morph.startAnimation();
    }
}

//设置初始滚动条位置
function initScroll() {
    //设置初始滚动条位置
    window.setTimeout(function () {
        var bounds = graph.getGraphBounds();
        var width = Math.max(bounds.width, graph.scrollTileSize.width * graph.view.scale);
        var height = Math.max(bounds.height, graph.scrollTileSize.height * graph.view.scale);
        graph.container.scrollTop = Math.floor(Math.max(0, bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4)));
        graph.container.scrollLeft = Math.floor(Math.max(0, bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2)));
    }, 0);
}
/****************************连接规则*******************************/
//连接边校验
function setConnectValidation() {
    // 连接边校验
    mxGraph.prototype.isValidConnection = function (source, target) {
        var sType = source.data.type;
        var tType = target.data.type;
        //开始节点只能连接审核节点
        if (sType == cellType.start) {
            return tType == cellType.audit;
        }
        //结束节点无连接
        if (sType == cellType.end) {
            return false;
        }
        //审核节点只能连接自己或结束节点
        if (sType == cellType.audit) {
            return tType == cellType.audit || tType == cellType.end;
        }
        return false;
    };
}
/****************************事件处理*******************************/
// 监听事件
function listenEvent() {
    //键盘事件
    keyEvent();
    //添加节点和连接线事件
    addCell();
    //连接改变事件
    connectChange();
    //双击事件
    doubleClick();
}

//键盘事件
function keyEvent() {
    //删除选中Cell或者Edge
    var keyHandler = new mxKeyHandler(graph);
    keyHandler.bindKey(46, function (evt) {
        if (graph.isEnabled()) {
            if (confirm("是否删除选择的节点")) {
                graph.removeCells();
            }
        }
    });
}

//添加节点和连接线事件
function addCell() {
    var isPart = function (cell) {
        var state = graph.view.getState(cell);
        var style = (state != null) ? state.style : graph.getCellStyle(cell);
        return style.constituent === 1;
    }

    //是否存在指定节点类型的数量
    var existNodeNumByType = function (type) {
        var num = 0;
        var cells = graph.getModel().cells;
        for (var key in cells) {
            if (cells[key].data && cells[key].data.type == type) {
                num++;
            }
        }
        return num;
    }

    //添加cell事件
    graph.addListener(mxEvent.CELLS_ADDED, function (sender, evt) {
        var cell = evt.properties.cells[0];
        if (isPart(cell)) {
            return;
        }
        if (cell.vertex) {
            //添加了一个节点
            //判断是否添加多个开始和结束节点
            if (cell.data.type == cellType.start || cell.data.type == cellType.end) {
                if (existNodeNumByType(cell.data.type) > 1) {
                    graph.removeCells([cell]);
                }
            }
        } else if (cell.edge) {
            //添加连接线
            cell.data = {
                label: "连接线",
                type: cellType.edge
            };
            cell.value = cell.data.label;
            //是否连接目标
            if (!evt.properties.target) {
            }
        }
    });
}

//连接改变事件
function connectChange() {
    graph.addListener(mxEvent.CONNECT_CELL, function (sender, evt) {
    });
}

//双击事件
function doubleClick() {
    //双击事件
    graph.addListener(mxEvent.DOUBLE_CLICK, function (sender, evt) {
        var cell = evt.properties.cell;
        if (cell != null) {
            //是否为连接线
            if (cell.isEdge()) {
                doubleClickEdge(cell);
            } else if (cell.isVertex()) {
                doubleClickVertex(cell);
            }
        }
    });
}

/**************************** 连接线双击事件 *******************************/
//双击连接线
function doubleClickEdge(cell) {
    if (confirm("是否改变连接线文字！")) {
        cell.data.label += "改变了";
        setDataAndLable(cell.data);
    }
}

/**************************** 节点双击事件 *******************************/
//双击节点
function doubleClickVertex(cell) {
    switch (cell.data.type.toString()) {
        case cellType.audit:
            auditNodeDoubleClik(cell);
            break;
    }
}
//审核节点双击事件
function auditNodeDoubleClik(cell) {
    if (confirm("节点是否审核！")) {
        cell.data.label = "已审核";
        cell.data.status = 1;
        setDataAndLable(cell.data);
    }
}

/**************************** 后台json数据转mxGraph格式的xml *******************************/
//json对象转xml
function jsonToXml(arr) {
    //xml转义
    var xmlTransferred = function (str) {
        return str.replace(new RegExp("&", 'g'), "&amp;")
            .replace(new RegExp("<", 'g'), "&lt;")
            .replace(new RegExp(">", 'g'), "&gt;")
            .replace(new RegExp("'", 'g'), "&apos;")
            .replace(new RegExp('"', 'g'), "&quot;")
            .replace(new RegExp('\n', 'g'), "&#x000A;");
    }

    var xml = '<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/>';
    //添加节点
    if (arr && arr.length > 0) {
        for (var i = 0; i < arr.length; i++) {
            var item = arr[i];
            switch (item.type.toString()) {
                case cellType.start://开始节点
                    item.label = item.label || "开始";
                    var jsonData = JSON.stringify({
                        type: cellType.start,
                        label: item.label
                    });
                    xml += '<mxCell id="' + item.id + '" value="' + xmlTransferred(item.label) + '" style="image=' + baseImgSrc + 'start-node.png;" vertex="1" parent="1">';
                    xml += '<mxGeometry x="' + item.pointX + '" y="' + item.pointY + '" width="50" height="50" as="geometry"/>';
                    xml += '<Object jsonData="' + xmlTransferred(jsonData) + '" as="data"/>';
                    xml += '</mxCell>';
                    break;
                case cellType.end://结束节点
                    item.label = item.label || "结束";
                    var jsonData = JSON.stringify({
                        type: cellType.end,
                        label: item.label
                    });
                    xml += '<mxCell id="' + item.id + '" value="' + xmlTransferred(item.label) + '" style="image=' + baseImgSrc + 'end-node.png;" vertex="1" parent="1">';
                    xml += '<mxGeometry x="' + item.pointX + '" y="' + item.pointY + '" width="50" height="50" as="geometry"/>';
                    xml += '<Object jsonData="' + xmlTransferred(jsonData) + '" as="data"/>';
                    xml += '</mxCell>';
                    break;
                case cellType.audit://审核节点
                    item.label = item.label || "审核节点";
                    var jsonData = JSON.stringify({
                        type: cellType.audit,
                        label: item.label
                    });
                    xml += '<mxCell id="' + item.id + '" value="' + xmlTransferred(item.label) + '" style="image=' + baseImgSrc + item.image + ';" vertex="1" parent="1">';
                    xml += '<mxGeometry x="' + item.pointX + '" y="' + item.pointY + '" width="50" height="50" as="geometry"/>';
                    xml += '<Object jsonData="' + xmlTransferred(jsonData) + '" as="data"/>';
                    xml += '</mxCell>';
                    break;
                case cellType.edge://连接线
                    item.label = item.label || "连接线";
                    var jsonData = JSON.stringify({
                        type: cellType.edge,
                        label: item.label
                    });
                    //连接线颜色设置
                    var style = "";
                    for (var j = 0; j < arr.length; j++) {
                        //目标节点类型为审核节点，并且已经审核过的
                        if (arr[j].id == item.targetId && arr[j].status == '1') {
                            style = 'style="strokeColor=#298f07;"';
                        }
                    }
                    xml += '<mxCell id="' + item.id + '" value="' + xmlTransferred(item.label) + '" edge="1" parent="1" source="' + item.sourceId + '" target="' + item.targetId + '" ' + style + ' >';
                    xml += '<mxGeometry relative="1" as="geometry">';
                    if (item.pointX && item.pointY) {
                        var xArr = item.pointX.split(",");
                        var yArr = item.pointY.split(",");
                        for (var k = 0; k < xArr.length; k++) {
                            xml += '<Array as="points"><mxPoint x="' + xArr[k] + '" y="' + yArr[k] + '"/></Array>';
                        }
                    }
                    xml += '</mxGeometry>';
                    xml += '<Object jsonData="' + xmlTransferred(jsonData) + '" as="data"/>';
                    xml += '</mxCell>';
                    break;
                default:
                    break;
            }
        }
    }
    xml += "</root></mxGraphModel>";
    return xml;
}

//导入，注意自定义数据data最好序列化，不然解析xml会出问题，例如： a=0001，解析后变成 a=1
function importModelXML(xmlTxt) {
    graph.getModel().beginUpdate();
    try {
        var doc = mxUtils.parseXml(xmlTxt);
        var root = doc.documentElement;
        var dec = new mxCodec(root.ownerDocument);
        dec.decode(root, graph.getModel());
    } finally {
        graph.getModel().endUpdate();
    }

    //转换自定义data为json对象
    Object.values(graph.getModel().cells).forEach(function (cell) {
        if (cell.data && cell.data.jsonData) {
            cell.data = JSON.parse(cell.data.jsonData);
        }
    });
}
/**************************** mxGraph导出的xml转后台json *******************************/
//xml对象转json
function xmlToJson() {
    var xmlDoc = exportXMLDocument();
    var arr = [];
    var rootXml = xmlDoc.childNodes[0].childNodes;
    for (var i = 0; i < rootXml.length; i++) {
        var mxCell = rootXml[i];
        if (mxCell.childNodes.length == 0) {
            continue;
        }
        var mxGeometry = mxCell.childNodes[0];
        var data = mxCell.childNodes[1];//获取自定义data数据
        switch (data.getAttribute("type").toString()) {
            case cellType.start://开始节点
            case cellType.end://结束节点
                arr.push({
                    id: mxCell.getAttribute("id"),//节点id
                    type: data.getAttribute("type"),//节点类型
                    label: data.getAttribute("label"),//节点名称
                    pointX: getX(mxGeometry),//节点x坐标
                    pointY: getY(mxGeometry),//节点y坐标
                });
                break;
            case cellType.edge://连接线
                var pointX = "";
                var pointY = "";
                if (mxGeometry.childNodes.length > 0 && mxGeometry.childNodes[0].childNodes.length > 0) {
                    var mxPoints = mxGeometry.childNodes[0].childNodes;
                    for (var k = 0; k < mxPoints.length; k++) {
                        pointX += getX(mxPoints[k]) + ',';
                        pointY += getY(mxPoints[k]) + ',';
                    }
                    if (pointX && pointX.length > 0) {
                        pointX = pointX.substring(0, pointX.length - 1);
                        pointY = pointY.substring(0, pointY.length - 1);
                    }
                }
                arr.push({
                    id: mxCell.getAttribute("id"),//连接线id
                    type: data.getAttribute("type"),//节点类型
                    label: data.getAttribute("label"),//节点名称
                    pointX: pointX,//连接线曲线坐标，逗号隔开
                    pointY: pointY,
                    sourceId: mxCell.getAttribute("source"),//起始节点id
                    targetId: mxCell.getAttribute("target"),//目标节点id
                });
                break;
            case cellType.audit://审核节点
                arr.push({
                    id: mxCell.getAttribute("id"),//节点id
                    type: data.getAttribute("type"),//节点类型
                    label: data.getAttribute("label"),//节点名称
                    pointX: getX(mxGeometry),//节点x坐标
                    pointY: getY(mxGeometry),//节点y坐标
                    image: 'audit-node.png',//审核节点图标
                    status: data.getAttribute("status"),//审核状态0未审核，1已审核
                });
                break;
            default:
        }
    }
    return arr
}
//获取mxGraph导出的xml对象
function exportXMLDocument() {
    var enc = new mxCodec(mxUtils.createXmlDocument());
    return enc.encode(graph.getModel());
}
//获取坐标x
function getX(mxGeometry) {
    var x = mxGeometry.getAttribute("x");
    if (x) {
        return x.substring(0, 9);//坐标值过长截断
    } else {
        return "0"
    }
}
//获取坐标y
function getY(mxGeometry) {
    var y = mxGeometry.getAttribute("y");
    if (y) {
        return y.substring(0, 9);//坐标值过长截断
    } else {
        return "0"
    }
}

/**************************** 获取和设置节点值 *******************************/
//获取选中的节点对象
function getCurrentNode() {
    return graph.getSelectionCell();
}

//获取当前选中节点的data克隆对象
function getCurrentNodeData() {
    return graph.getSelectionCell().clone().data;
}

//设置选中的自定义值和显示值
function setDataAndLable(data) {
    graph.getModel().beginUpdate();
    try {
        if (!graph.isSelectionEmpty()) {
            var _cell = graph.getSelectionCell();
            _cell.data = data;
            _cell.setValue(data.label);
            graph.refresh(_cell);
        }
    }
    finally {
        graph.getModel().endUpdate();
    }
}
/**************************** mxGraph导出的xml字符串 *******************************/
//导出mxGraph的xml格式化字符串
function exportXMLTxt() {
    return mxUtils.getPrettyXml(exportXMLDocument());
}